Autosumme in ALV-Grid – Version 1

In einem Projekt wurde eine Anwendung programmiert, in der viele numerische Daten berechnet und ausgegeben wurden. Die Berechnungsergebnisse wurden auf mehrere Zellen verteilt. Um überprüfen zu können, ob die Verteilung richtig programmiert war, musste man die Summe über die verteilten Werte bilden und mit dem Ausgangswert vergleichen. Dies war immer relativ mühselig, da man entweder die Werte per Copy&Paste nach Excel kopieren musste (dann gab es aber Probleme bei negativen Zahlen…!) oder man musste die komplette Tabelle in Excel öffnen. Alles natürlich möglich, aber mir doch etwas zu umständlich.

Ich erinnerte mich an das Event delayed_changed_sel_callback, mit dem man eine Selektion im Grid verzögert auswerten konnte. Mit Hilfe der Tastenkombination STRG+Y kann man einzelne Zellen markieren. Diese Funktionen wollte ich nutzen, um die markierten Zellenwerte auszulesen und zu summieren.

Herausgekommen ist das unten stehende Programm. Der eigentliche Teil, die Summierung der Zellen, steht komplett in der Methode HANDLE_DELAYED_SELECTION.

2016-10-07_19-06-25

Vorgehen

Event delayed_changed_sel_callback registrieren und einen Eventhandler in der Klasse zuordnen (Handle_Delayed_Selection).

Wie kommt man an die markierten Zellen heran? Dafür gibt es die Methode Get_Selected_Cells. Sie liefert eine Tabelle zurück, in der die markierten Feldnamen und der Zeilenindex stehen. Mittels READ TABLE und ASSIGN COMPONENT kann man also auf einen Zellwert zugreifen.

Nun muss noch mittels DESCRIBE FIELD geprüft werden, ob es sich um ein Feld mit einem numerischen Wert handelt. Wenn das der Fall ist, kann der Wert der Zelle aufsummiert werden.

 

Normale Zellen vs. Summenzeilen

Das Vorgehen bei einem ALV-Grid, in dem keine Summen oder Zwischensummen gebildet wurden, ist einfach und erfolgt nach dem oben genannten Schema. Eine Herausforderung sind jedoch die (Zwischen-) Summenstufen gewesen. Diese werden im ALV-Grid in eigenen – geschützten – Tabellen verwaltet:

Tabelle Bedeutung Bemerkung
MT_CT00 Summetabelle Sie enthält in der Regel nur einen Eintrag. Ausnahme: Es sind in der Summierung der Feldwerte unterschiedliche Einheiten vorhanden.
MT_CT01 Zwischensummentabelle 1 Erste Zwischensummenstufe
MT_CTnn Zwischensummentabelle n Tabellen für Zwischensummenstufe nn
MT_CT09 Zwischensummentabelle 9 9 ist die höchste Stufe. Mehr Zwischensummen können nicht erstellt werden.

Immerhin gibt es in diesem Fall eine Methode, mit der man sich die Zwischensummentabellen – bzw. eine Referenz auf diese – besorgen kann: Get_Subtotals. Im Feld ROW_ID-ROWTYPE der Zellen-Tabelle steht, ob es sich um die Totals-Tabelle handelt (1. Zeichen = T) oder eine Zwischensumme (1. Zeichen = S). Nicht gruppierte Zellen haben den Eintrag SPACE.

Eine Selektion auf ein Summenfeld liefert zum Beispiel diesen ROWTYPE: S 0101X0000000001. Der Vierstellige Code nach dem S sagt aus, um welche Hierarchiestufe es sich handelt (Stellen 1 und 2 des Codes). Die Stellen 3 und 4 des Codes sagen aus, in welcher Tabelle das markierte Feld steht. In diesem Fall ist es Level 1 der Hierarchie und Tabelle MT_CT01.

Anhand dieses Code kann man also herausfinden, in welcher Zwischensummentabelle nachgeschaut werden muss. Dies tue ich hier:

lv_index = ls_cell-row_id-rowtype+4(2).
lv_tablename = 'LD_CT' && lv_index.
ASSIGN (lv_tablename) TO <ref_data>.

Nachdem wir nun wissen, in welcher Tabelle wir nachsehen müssen um den markierten Zellwert zu finden, müssen wir nun noch den richtigen Index ermitteln. Dieser wird leider nicht mitgegeben, sondern muss aus der Tabelle GROUPLEVELS, die über Get_Subtotals geliefert wird, ermittelt werden:

READ TABLE lt_grouplevels INTO ls_grouplevel INDEX ls_cell-row_id-index.
IF sy-subrc = 0.
  ls_cell-row_id-index = ls_grouplevel-cindx_from.
ENDIF.

Achtung! Das Programm funktioniert nur, wenn es sich um reine (Zwischen-) Summen handelt! Zwischensummen, die aus mehreren Zeilen bestehen weil sich die zugehörige Einheit unterscheidet, können (noch) nicht erkannt werden. Hier muss ich noch etwas forschen…

Zwischensummen bilden

Ein kurzer Hinweis, wie man im ALV-Grid Zwischensummen bildet:

Wähle als erstes mindestens eine Spalte über die du dann mit Hilfe des Summenicons 2016-10-07_19-48-00 eine Summe bildest. Danach kannst du weitere Spalten markieren und mit dem Zwischensummenicon 2016-10-07_19-49-31 die Spalten definieren, über die zusätzlich eine Zwischensumme erstellt werden soll.

Aufrisssummenstufe

Über die Aufrisssummenstufe kannst du einfach festlegen, dass nur Zwischensummenzeilen einer bestimmten Hierarchie angezeigt werden sollen:

Auswahl im Menü:

2016-10-07_19-41-49

Auswahl der Hierarchieebene:

2016-10-07_19-06-45

Anzeige der gewählten Zwischensummen:

2016-10-07_19-22-39

Code

REPORT zz_alv_autosumme.

PARAMETERS p_total TYPE p DECIMALS 2.

CLASS lcl_main DEFINITION.

 PUBLIC SECTION.
 METHODS start.
 PROTECTED SECTION.
 DATA mr_grid TYPE REF TO cl_gui_alv_grid.
 DATA mt_data TYPE STANDARD TABLE OF spfli.
 DATA mv_data_table TYPE tabname VALUE 'SPFLI'.
 DATA mr_dock TYPE REF TO cl_gui_docking_container.
 METHODS create_docker.
 METHODS create_grid.
 METHODS handle_delayed_selection
 FOR EVENT delayed_changed_sel_callback
 OF cl_gui_alv_grid
 IMPORTING sender.
 METHODS register_events.
 METHODS select_data.
ENDCLASS.

CLASS lcl_main IMPLEMENTATION.

 METHOD start.
 select_data( ).
 create_docker( ).
 create_grid( ).
 register_events( ).
 ENDMETHOD.

 METHOD create_docker.
 "Create Docking container at bottom
 CREATE OBJECT mr_dock
 EXPORTING
 side = cl_gui_docking_container=>dock_at_bottom
 ratio = 90
 no_autodef_progid_dynnr = abap_false.

 ENDMETHOD.

 METHOD create_grid.
 "Create ALV-Grid
 CREATE OBJECT mr_grid
 EXPORTING
 i_appl_events = abap_true
 i_parent = mr_dock.

 "and display data
 mr_grid->set_table_for_first_display(
 EXPORTING
 i_structure_name = mv_data_table
 CHANGING
 it_outtab = mt_data ).

 "Set focus on grid so user can directly scroll and select cells via CTRL+Y
 cl_gui_container=>set_focus( mr_grid ).

 ENDMETHOD.
 METHOD handle_delayed_selection.

 "Local data
 DATA lt_cells TYPE lvc_t_cell.
 DATA ls_cell LIKE LINE OF lt_cells.
 DATA lv_total TYPE p DECIMALS 2.
 DATA lv_val_type TYPE c.
 DATA lv_index TYPE n LENGTH 2.
 DATA lv_tablename TYPE string.
 DATA lt_grouplevels TYPE lvc_t_grpl.
 DATA ls_grouplevel LIKE LINE OF lt_grouplevels.

 FIELD-SYMBOLS <ref_data> TYPE REF TO data.
 FIELD-SYMBOLS <table> TYPE table.
 FIELD-SYMBOLS <warea> TYPE any.
 FIELD-SYMBOLS <val> TYPE any.

 "data references to sub totals tables
 DATA ld_ct00 TYPE REF TO data.
 DATA ld_ct01 TYPE REF TO data.
 DATA ld_ct02 TYPE REF TO data.
 DATA ld_ct03 TYPE REF TO data.
 DATA ld_ct04 TYPE REF TO data.
 DATA ld_ct05 TYPE REF TO data.
 DATA ld_ct06 TYPE REF TO data.
 DATA ld_ct07 TYPE REF TO data.
 DATA ld_ct08 TYPE REF TO data.
 DATA ld_ct09 TYPE REF TO data.

 "get selected cells (selection via CTRL + Y)
 sender->get_selected_cells( IMPORTING et_cell = lt_cells ).

 "If there is only one cell selected, we do not need to sum that...
 CHECK lines( lt_cells ) > 1.

 "Read all cell values
 LOOP AT lt_cells INTO ls_cell.

 "in case of rowtype (normal cell, total or subtotal) assign correct data table
 CASE ls_cell-row_id-rowtype(1).
 "Total sum of all
 WHEN 'T'.
 sender->get_subtotals( IMPORTING ep_collect00 = ld_ct00 ).

 ASSIGN ld_ct00 TO <ref_data>.
 ls_cell-row_id-index = 1.
 "assign specified data table
 ASSIGN <ref_data>->* TO <table>.

 "subtotals
 WHEN 'S'.
 sender->get_subtotals( IMPORTING
 ep_collect01 = ld_ct01
 ep_collect02 = ld_ct02
 ep_collect03 = ld_ct03
 ep_collect04 = ld_ct04
 ep_collect05 = ld_ct05
 ep_collect06 = ld_ct06
 ep_collect07 = ld_ct07
 ep_collect08 = ld_ct08
 ep_collect09 = ld_ct09
 et_grouplevels = lt_grouplevels ).

 lv_index = ls_cell-row_id-rowtype+4(2).
 lv_tablename = 'LD_CT' && lv_index.
 ASSIGN (lv_tablename) TO <ref_data>.

 READ TABLE lt_grouplevels INTO ls_grouplevel INDEX ls_cell-row_id-index.
 IF sy-subrc = 0.
 ls_cell-row_id-index = ls_grouplevel-cindx_from.
 ENDIF.
 "assign specified data table
 ASSIGN <ref_data>->* TO <table>.

 "Normal cell value
 WHEN space.
 ASSIGN mt_data TO <table>.
 ENDCASE.


 "Only read table line when index changes
 AT NEW row_id.
 READ TABLE <table> ASSIGNING <warea> INDEX ls_cell-row_id-index.
 ENDAT.
 "Assign selected fieldname of workarea
 ASSIGN COMPONENT ls_cell-col_id OF STRUCTURE <warea> TO <val>.
 IF sy-subrc = 0.
 "check correct type of field: Only numeric fields will be taken
 DESCRIBE FIELD <val> TYPE lv_val_type.
 CASE lv_val_type.
 WHEN 'P' "Packed
 OR 'N' "Numchar
 OR 'b' "Integer
 OR 'a' "decfloat
 OR 'e' "decfloat
 OR 'F'. "Float?
 "add cell value to total
 ADD <val> TO lv_total.
 ENDCASE.
 ENDIF.
 ENDLOOP.

 IF lv_total IS NOT INITIAL.
 "There were numeric fields selected and therefor we have a total to show:
 MESSAGE s000(oo) WITH 'TOTAL:' space lv_total.
 "Parameterfeld ebenfalls füllen
 p_total = lv_total.
 ENDIF.
 ENDMETHOD.

 METHOD register_events.
 "Set handler
 SET HANDLER handle_delayed_selection FOR mr_grid.
 "register event for delayed selection
 mr_grid->register_delayed_event( mr_grid->mc_evt_delayed_change_select ).
 ENDMETHOD.

 METHOD select_data.
 "Select data
 SELECT * FROM (mv_data_table) INTO TABLE mt_data UP TO 100 ROWS.
 ENDMETHOD.

ENDCLASS.

INITIALIZATION.
 DATA(gr_main) = NEW lcl_main( ).
 gr_main->start( ).
Enno Wulff